slimecing

a fighting game featuring slimes and swords
Log | Files | Refs | README

InputControlVisualizer.cs (13275B)


      1 using System;
      2 using System.Collections.Generic;
      3 using UnityEngine.InputSystem.Layouts;
      4 using UnityEngine.InputSystem.LowLevel;
      5 
      6 ////TODO: add way to plot values over time
      7 
      8 // Goal is to build this out into something that can visualize a large number of
      9 // aspects about an InputControl/InputDevice especially with an eye towards making
     10 // it a good deal to debug any input collection/processing irregularities that may
     11 // be seen in players (or the editor, for that matter).
     12 
     13 // Some fields assigned through only through serialization.
     14 #pragma warning disable CS0649
     15 
     16 namespace UnityEngine.InputSystem.Samples
     17 {
     18     /// <summary>
     19     /// A component for debugging purposes that adds an on-screen display which shows
     20     /// activity on an input control over time.
     21     /// </summary>
     22     /// <remarks>
     23     /// This component is most useful for debugging input directly on the source device.
     24     /// </remarks>
     25     /// <seealso cref="InputActionVisualizer"/>
     26     [AddComponentMenu("Input/Debug/Input Control Visualizer")]
     27     [ExecuteInEditMode]
     28     public class InputControlVisualizer : InputVisualizer
     29     {
     30         /// <summary>
     31         /// What kind of visualization to show.
     32         /// </summary>
     33         public Mode visualization
     34         {
     35             get => m_Visualization;
     36             set
     37             {
     38                 if (m_Visualization == value)
     39                     return;
     40                 m_Visualization = value;
     41                 SetupVisualizer();
     42             }
     43         }
     44 
     45         /// <summary>
     46         /// Path of the control that is to be visualized.
     47         /// </summary>
     48         /// <seealso cref="InputControlPath"/>
     49         /// <seealso cref="InputControl.path"/>
     50         public string controlPath
     51         {
     52             get => m_ControlPath;
     53             set
     54             {
     55                 m_ControlPath = value;
     56                 if (m_Control != null)
     57                     ResolveControl();
     58             }
     59         }
     60 
     61         /// <summary>
     62         /// If, at runtime, multiple controls are matching <see cref="controlPath"/>, this property
     63         /// determines the index of the control that is retrieved from the possible options.
     64         /// </summary>
     65         public int controlIndex
     66         {
     67             get => m_ControlIndex;
     68             set
     69             {
     70                 m_ControlIndex = value;
     71                 if (m_Control != null)
     72                     ResolveControl();
     73             }
     74         }
     75 
     76         /// <summary>
     77         /// The control resolved from <see cref="controlPath"/> at runtime. May be null.
     78         /// </summary>
     79         public InputControl control => m_Control;
     80 
     81         protected new void OnEnable()
     82         {
     83             if (m_Visualization == Mode.None)
     84                 return;
     85 
     86             if (s_EnabledInstances == null)
     87                 s_EnabledInstances = new List<InputControlVisualizer>();
     88             if (s_EnabledInstances.Count == 0)
     89             {
     90                 InputSystem.onDeviceChange += OnDeviceChange;
     91                 InputSystem.onEvent += OnEvent;
     92             }
     93             s_EnabledInstances.Add(this);
     94 
     95             ResolveControl();
     96 
     97             base.OnEnable();
     98         }
     99 
    100         protected new void OnDisable()
    101         {
    102             if (m_Visualization == Mode.None)
    103                 return;
    104 
    105             s_EnabledInstances.Remove(this);
    106             if (s_EnabledInstances.Count == 0)
    107             {
    108                 InputSystem.onDeviceChange -= OnDeviceChange;
    109                 InputSystem.onEvent -= OnEvent;
    110             }
    111 
    112             m_Control = null;
    113 
    114             base.OnDisable();
    115         }
    116 
    117         protected new void OnGUI()
    118         {
    119             if (m_Visualization == Mode.None)
    120                 return;
    121 
    122             base.OnGUI();
    123         }
    124 
    125         protected new void OnValidate()
    126         {
    127             ResolveControl();
    128             base.OnValidate();
    129         }
    130 
    131         [Tooltip("The type of visualization to perform for the control.")]
    132         [SerializeField] private Mode m_Visualization;
    133         [Tooltip("Path of the control that should be visualized. If at runtime, multiple "
    134             + "controls match the given path, the 'Control Index' property can be used to decide "
    135             + "which of the controls to visualize.")]
    136         [InputControl, SerializeField] private string m_ControlPath;
    137         [Tooltip("If multiple controls match 'Control Path' at runtime, this property decides "
    138             + "which control to visualize from the list of candidates. It is a zero-based index.")]
    139         [SerializeField] private int m_ControlIndex;
    140 
    141         [NonSerialized] private InputControl m_Control;
    142 
    143         private static List<InputControlVisualizer> s_EnabledInstances;
    144 
    145         private void ResolveControl()
    146         {
    147             m_Control = null;
    148             if (string.IsNullOrEmpty(m_ControlPath))
    149                 return;
    150 
    151             using (var candidates = InputSystem.FindControls(m_ControlPath))
    152             {
    153                 var numCandidates = candidates.Count;
    154                 if (numCandidates > 1 && m_ControlIndex < numCandidates && m_ControlIndex >= 0)
    155                     m_Control = candidates[m_ControlIndex];
    156                 else if (numCandidates > 0)
    157                     m_Control = candidates[0];
    158             }
    159 
    160             SetupVisualizer();
    161         }
    162 
    163         private void SetupVisualizer()
    164         {
    165             if (m_Control == null)
    166             {
    167                 m_Visualizer = null;
    168                 return;
    169             }
    170 
    171             switch (m_Visualization)
    172             {
    173                 case Mode.Value:
    174                 {
    175                     var valueType = m_Control.valueType;
    176                     if (valueType == typeof(Vector2))
    177                         m_Visualizer = new VisualizationHelpers.Vector2Visualizer(m_HistorySamples);
    178                     else if (valueType == typeof(float))
    179                         m_Visualizer = new VisualizationHelpers.ScalarVisualizer<float>(m_HistorySamples)
    180                         {
    181                             ////TODO: pass actual min/max limits of control
    182                             limitMax = 1,
    183                             limitMin = 0
    184                         };
    185                     else if (valueType == typeof(int))
    186                         m_Visualizer = new VisualizationHelpers.ScalarVisualizer<int>(m_HistorySamples)
    187                         {
    188                             ////TODO: pass actual min/max limits of control
    189                             limitMax = 1,
    190                             limitMin = 0
    191                         };
    192                     else
    193                     {
    194                         ////TODO: generic visualizer
    195                     }
    196                     break;
    197                 }
    198 
    199                 case Mode.Events:
    200                 {
    201                     var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples)
    202                     {
    203                         timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames,
    204                         historyDepth = m_HistorySamples,
    205                         showLimits = true,
    206                         limitsY = new Vector2(0, 5) // Will expand upward automatically
    207                     };
    208                     m_Visualizer = visualizer;
    209                     visualizer.AddTimeline("Events", Color.green,
    210                         VisualizationHelpers.TimelineVisualizer.PlotType.BarChart);
    211                     break;
    212                 }
    213 
    214                 case Mode.MaximumLag:
    215                 {
    216                     var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples)
    217                     {
    218                         timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames,
    219                         historyDepth = m_HistorySamples,
    220                         valueUnit = new GUIContent("ms"),
    221                         showLimits = true,
    222                         limitsY = new Vector2(0, 6)
    223                     };
    224                     m_Visualizer = visualizer;
    225                     visualizer.AddTimeline("MaxLag", Color.red,
    226                         VisualizationHelpers.TimelineVisualizer.PlotType.BarChart);
    227                     break;
    228                 }
    229 
    230                 case Mode.Bytes:
    231                 {
    232                     var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples)
    233                     {
    234                         timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames,
    235                         valueUnit = new GUIContent("bytes"),
    236                         historyDepth = m_HistorySamples,
    237                         showLimits = true,
    238                         limitsY = new Vector2(0, 64)
    239                     };
    240                     m_Visualizer = visualizer;
    241                     visualizer.AddTimeline("Bytes", Color.red,
    242                         VisualizationHelpers.TimelineVisualizer.PlotType.BarChart);
    243                     break;
    244                 }
    245 
    246                 default:
    247                     throw new NotImplementedException();
    248             }
    249         }
    250 
    251         private static void OnDeviceChange(InputDevice device, InputDeviceChange change)
    252         {
    253             if (change != InputDeviceChange.Added && change != InputDeviceChange.Removed)
    254                 return;
    255 
    256             for (var i = 0; i < s_EnabledInstances.Count; ++i)
    257             {
    258                 var component = s_EnabledInstances[i];
    259                 if (change == InputDeviceChange.Removed && component.m_Control != null &&
    260                     component.m_Control.device == device)
    261                     component.ResolveControl();
    262                 else if (change == InputDeviceChange.Added)
    263                     component.ResolveControl();
    264             }
    265         }
    266 
    267         private static void OnEvent(InputEventPtr eventPtr, InputDevice device)
    268         {
    269             // Ignore very first update as we usually get huge lag spikes and event count
    270             // spikes in it from stuff that has accumulated while going into play mode or
    271             // starting up the player.
    272             if (InputState.updateCount <= 1)
    273                 return;
    274 
    275             if (InputState.currentUpdateType == InputUpdateType.Editor)
    276                 return;
    277 
    278             if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
    279                 return;
    280 
    281             for (var i = 0; i < s_EnabledInstances.Count; ++i)
    282             {
    283                 var component = s_EnabledInstances[i];
    284                 if (component.m_Control?.device != device || component.m_Visualizer == null)
    285                     continue;
    286 
    287                 component.OnEventImpl(eventPtr);
    288             }
    289         }
    290 
    291         private unsafe void OnEventImpl(InputEventPtr eventPtr)
    292         {
    293             switch (m_Visualization)
    294             {
    295                 case Mode.Value:
    296                 {
    297                     var statePtr = m_Control.GetStatePtrFromStateEvent(eventPtr);
    298                     if (statePtr == null)
    299                         return; // No value for control in event.
    300                     var value = m_Control.ReadValueFromStateAsObject(statePtr);
    301                     m_Visualizer.AddSample(value, eventPtr.time);
    302                     break;
    303                 }
    304 
    305                 case Mode.Events:
    306                 {
    307                     var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer;
    308                     var frame = (int)InputState.updateCount;
    309                     ref var valueRef = ref visualizer.GetOrCreateSample(0, frame);
    310                     var value = valueRef.ToInt32() + 1;
    311                     valueRef = value;
    312                     visualizer.limitsY =
    313                         new Vector2(0, Mathf.Max(value, visualizer.limitsY.y));
    314                     break;
    315                 }
    316 
    317                 case Mode.MaximumLag:
    318                 {
    319                     var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer;
    320                     var lag = (Time.realtimeSinceStartup - eventPtr.time) * 1000; // In milliseconds.
    321                     var frame = (int)InputState.updateCount;
    322                     ref var valueRef = ref visualizer.GetOrCreateSample(0, frame);
    323 
    324                     if (lag > valueRef.ToDouble())
    325                     {
    326                         valueRef = lag;
    327                         if (lag > visualizer.limitsY.y)
    328                             visualizer.limitsY = new Vector2(0, Mathf.Ceil((float)lag));
    329                     }
    330                     break;
    331                 }
    332 
    333                 case Mode.Bytes:
    334                 {
    335                     var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer;
    336                     var frame = (int)InputState.updateCount;
    337                     ref var valueRef = ref visualizer.GetOrCreateSample(0, frame);
    338                     var value = valueRef.ToInt32() + eventPtr.sizeInBytes;
    339                     valueRef = value;
    340                     visualizer.limitsY =
    341                         new Vector2(0, Mathf.Max(value, visualizer.limitsY.y));
    342                     break;
    343                 }
    344             }
    345         }
    346 
    347         /// <summary>
    348         /// Determines which aspect of the control should be visualized.
    349         /// </summary>
    350         public enum Mode
    351         {
    352             None = 0,
    353             Value = 1,
    354             Events = 4,
    355             MaximumLag = 6,
    356             Bytes = 7,
    357         }
    358     }
    359 }